package solver

import (
	"fmt"
	"gosudoku/set"
	"log"
	"slices"
	"strings"
)

/*
TYPE 1(almost locked pair, implemented):
P2 XY...------------H1 XY...

	 |
	N1 XY

TYPE 2(almost locked triple, implemented):
P2 XY...------------H2 XYZ...

	 |
	N1 XY

TYPE 3(almost locked triple, implemented):
P2 XY...------------H2 XYW...

	 |
	N2 XYZ

TYPE 4(almost locked triple, to be done):
P2 XY...------------H1 XY...

	 |
	N2 XYZ...

TYPE 5(almost locked triple, to be done):
P2 XYZ...------------H2 XYZ...

	 |
	N2 XYZ

TYPE 6(almost locked triple, to be done):
P3 XYZ...------------H1 XYZ...

	 |
	N1 XYZ

Sue de Coq

P2 ABCD -------------N1 CD
   |
   N1 AB

*/

func (g *Grid) AlmostLockedPair() (used bool) {
	return g.AlmostLockedSubset(g.ALSType1)
}
func (g *Grid) AlmostLockedSubset(alFunc func([]*Cell, House, House) bool) (used bool) {
	for boxNum := 0; boxNum < 9; boxNum++ {
		box := g.boxes[boxNum]
		rowStart := boxNum / 3 * 3
		colStart := boxNum % 3 * 3
		for rowNum := rowStart; rowNum < rowStart+3; rowNum++ {
			row := g.rows[rowNum]
			cells := row.cells[colStart : colStart+3]
			if alFunc(cells, row, box) {
				return true
			}
			if alFunc(cells, box, row) {
				return true
			}
		}
		for colNum := colStart; colNum < colStart+3; colNum++ {
			col := g.cols[colNum]
			cells := col.cells[rowStart : rowStart+3]

			if alFunc(cells, col, box) {
				return true
			}
			if alFunc(cells, box, col) {
				return true
			}
		}
	}
	return
}

func (g *Grid) AlmostLockedSubsetType2() (used bool) {
	return g.AlmostLockedSubset(g.ALSType2)
}

func (g *Grid) AlmostLockedSubsetType3() (used bool) {
	return g.AlmostLockedSubset(g.ALSType3)
}

func (g *Grid) AlmostLockedSubsetType4() (used bool) {
	return g.AlmostLockedSubset(g.ALSType4)
}

func (g *Grid) AlmostLockedSubsetType5() (used bool) {
	return g.AlmostLockedSubset(g.ALSType5)
}

func (g *Grid) AlmostLockedSubsetType6() (used bool) {
	return g.AlmostLockedSubset(g.ALSType6)
}

func (g *Grid) ALSType1(shares []*Cell,
	nakedHouse, hiddenHouse House) (used bool) {
	for _, pivotCells := range combine(getUnsolvedCells(shares), 2) {
		nok, nakedCells, nakedCands := verifyNakedType1(pivotCells, shares, nakedHouse)
		//fmt.Println(nakedCells, nakedCands, pivotCells)
		if !nok {
			continue
		}
		hok, hiddenCells, hiddenCands := verifyHiddenType1(
			nakedCands, pivotCells, hiddenHouse)
		if !hok {
			continue
		}
		if g.removeALS(shares, nakedCells, nakedHouse, nakedCands,
			hiddenCells, hiddenCands) {
			used = true
			logALS(pivotCells, nakedCells, nakedCands, hiddenCells,
				hiddenCands, "Almost Locked Pair")
		}
		return
	}

	return
}

func verifyNakedType1(pivotCells, shares []*Cell, nakedHouse House) (
	nok bool, nakedCells []*Cell, nakedCands set.Set) {
	pivotCands := pivotCells[0].cands.Union(pivotCells[1].cands)
	for _, cell := range nakedHouse.cells {
		if slices.Contains(shares, cell) || cell.solved() {
			continue
		}
		if cell.cands.Count() != 2 || !pivotCands.IsSuperset(cell.cands) {
			continue
		}
		nakedCands = cell.cands
		if pivotCells[0].cands.Intersect(nakedCands).Count() == 0 ||
			pivotCells[1].cands.Intersect(nakedCands).Count() == 0 {
			continue
		}
		nok = true
		nakedCells = append(nakedCells, cell)
		return
	}

	return
}

func verifyHiddenType1(nakedCands set.Set, pivotCells []*Cell, hiddenHouse House) (
	hok bool, hiddenCells []*Cell, hiddenCands set.Set) {
	//fmt.Println("nakedCands", nakedCands, pivotCells)
	for _, cell := range hiddenHouse.cells {
		if slices.Contains(pivotCells, cell) || cell.solved() {
			continue
		}
		if cell.cands.Intersect(nakedCands).Count() != 0 {
			hiddenCells = append(hiddenCells, cell)
		}
	}
	if len(hiddenCells) != 1 {
		return
	}
	hok = true
	hiddenCands = nakedCands
	return
}

func (g *Grid) ALSType2(shares []*Cell,
	nakedHouse, hiddenHouse House) (used bool) {
	for _, pivotCells := range combine(getUnsolvedCells(shares), 2) {
		nok, nakedCells, nakedCands := verifyNakedType1(pivotCells, shares, nakedHouse)
		//fmt.Println(nakedCells, nakedCands, pivotCells)
		if !nok {
			continue
		}
		hok, hiddenCells, hiddenCands := verifyHiddenType2(
			nakedCands, shares, pivotCells, hiddenHouse)
		if !hok {
			continue
		}
		if g.removeALS(shares, nakedCells, nakedHouse, nakedCands,
			hiddenCells, hiddenCands) {
			used = true
			logALS(pivotCells, nakedCells, nakedCands, hiddenCells,
				hiddenCands, "Almost Locked Triple 2")
		}
		return
	}

	return
}

func verifyHiddenType2(nakedCands set.Set, shares, pivotCells []*Cell, hiddenHouse House) (
	hok bool, hiddenCells []*Cell, hiddenCands set.Set) {
	cells := []*Cell{}
	for _, cell := range hiddenHouse.cells {
		if cell.solved() || slices.Contains(shares, cell) {
			continue
		}
		cells = append(cells, cell)
	}
	for _, hiddenCells = range combine(cells, 2) {
		otherCands := set.NewSet()
		hiddenCands = set.NewSet()
		//fmt.Println("hiddenCands", hiddenCands, "otherCands", otherCands)
		for _, cell := range hiddenHouse.cells {
			if cell.solved() || slices.Contains(pivotCells, cell) {
				continue
			}
			if slices.Contains(hiddenCells, cell) {
				hiddenCands = hiddenCands.Union(cell.cands)
			} else {
				otherCands = otherCands.Union(cell.cands)
			}
			//fmt.Println(cell, "hiddenCands", hiddenCands, "otherCands", otherCands)
		}
		//fmt.Println("hiddenCells", hiddenCells, hiddenCands, otherCands)
		hiddenCands = hiddenCands.Diff(otherCands)
		if hiddenCands.Count() != 3 || !hiddenCands.IsSuperset(nakedCands) {
			continue
		}
		pcands := pivotCells[0].cands.Union(pivotCells[1].cands)
		if pcands.Diff(nakedCands).Intersect(hiddenCands).Count() != 0 {
			continue
		}
		hok = true
		return
	}
	return
}

func (g *Grid) ALSType3(shares []*Cell,
	nakedHouse, hiddenHouse House) (used bool) {
	for _, pivotCells := range combine(getUnsolvedCells(shares), 2) {
		nok, nakedCells, nakedCands := verifyNakedType2(pivotCells, shares, nakedHouse)
		if !nok {
			continue
		}
		hok, hiddenCells, hiddenCands := verifyHiddenType2(
			nakedCands, shares, pivotCells, hiddenHouse)
		//fmt.Println(nakedHouse.kind, nakedCells, nakedCands, pivotCells)
		//fmt.Println(hiddenHouse.kind, hiddenCells, hiddenCands, hok)
		if !hok {
			continue
		}
		if g.removeALS(shares, nakedCells, nakedHouse, nakedCands,
			hiddenCells, hiddenCands) {
			used = true
			logALS(pivotCells, nakedCells, nakedCands, hiddenCells,
				hiddenCands, "Almost Locked Triple 3")
		}
		return
	}

	return
}

func (g *Grid) ALSType4(shares []*Cell,
	nakedHouse, hiddenHouse House) (used bool) {
	for _, pivotCells := range combine(getUnsolvedCells(shares), 2) {
		nok, nakedCells, nakedCands := verifyNakedType2(pivotCells, shares, nakedHouse)
		if !nok {
			continue
		}
		pivotCands := pivotCells[0].cands.Union(pivotCells[1].cands).Intersect(
			nakedCands)
		hok, hiddenCells, hiddenCands := verifyHiddenType1(
			pivotCands, shares, hiddenHouse)
		//fmt.Println(nakedHouse.kind, nakedCells, nakedCands, pivotCells)
		//fmt.Println(hiddenHouse.kind, hiddenCells, hiddenCands, hok)
		if !hok {
			continue
		}
		if g.removeALS(shares, nakedCells, nakedHouse, nakedCands,
			hiddenCells, hiddenCands) {
			used = true
			logALS(pivotCells, nakedCells, nakedCands, hiddenCells,
				hiddenCands, "Almost Locked Triple 4")
		}
		return
	}

	return
}

func (g *Grid) ALSType5(shares []*Cell,
	nakedHouse, hiddenHouse House) (used bool) {
	for _, pivotCells := range combine(getUnsolvedCells(shares), 2) {
		nok, nakedCells, nakedCands := verifyNakedType3(pivotCells, shares, nakedHouse)
		if !nok {
			continue
		}
		//fmt.Println(nakedHouse.kind, nakedCells, nakedCands, pivotCells)
		//fmt.Println(hiddenHouse.kind, hiddenCells, hiddenCands, hok)
		hok, hiddenCells, hiddenCands := verifyHiddenType3(
			nakedCands, shares, pivotCells, hiddenHouse)

		if !hok {
			continue
		}
		if g.removeALS(shares, nakedCells, nakedHouse, nakedCands,
			hiddenCells, hiddenCands) {
			used = true
			logALS(pivotCells, nakedCells, nakedCands, hiddenCells,
				hiddenCands, "Almost Locked Triple 5")
		}
		return
	}

	return
}

func (g *Grid) ALSType6(shares []*Cell,
	nakedHouse, hiddenHouse House) (used bool) {
	pivotCells := getUnsolvedCells(shares)
	if len(pivotCells) != 3 {
		return
	}
	nok, nakedCells, nakedCands := verifyNakedType6(pivotCells, nakedHouse)
	if !nok {
		return
	}
	//fmt.Println(nakedHouse.kind, nakedCells, nakedCands, pivotCells)
	//fmt.Println(hiddenHouse.kind, hiddenCells, hiddenCands, hok)
	hok, hiddenCells, hiddenCands := verifyHiddenType6(
		nakedCands, pivotCells, hiddenHouse)

	if !hok {
		return
	}
	if g.removeALS(shares, nakedCells, nakedHouse, nakedCands,
		hiddenCells, hiddenCands) {
		used = true
		logALS(pivotCells, nakedCells, nakedCands, hiddenCells,
			hiddenCands, "Almost Locked Triple 6")
	}
	return
}

func verifyNakedType2(pivotCells, shares []*Cell, nakedHouse House) (
	nok bool, nakedCells []*Cell, nakedCands set.Set) {
	pivotCands := pivotCells[0].cands.Union(pivotCells[1].cands)
	cells := []*Cell{}
	for _, cell := range nakedHouse.cells {
		if slices.Contains(shares, cell) || cell.solved() {
			continue
		}
		cells = append(cells, cell)
	}
	for _, nakedCells = range combine(cells, 2) {
		nakedCands = nakedCells[0].cands.Union(nakedCells[1].cands)
		if nakedCands.Count() != 3 {
			continue
		}
		pcands := pivotCands.Intersect(nakedCands)
		if pcands.Count() != 2 {
			continue
		}
		if nakedCells[0].cands.Diff(pcands).Count() == 0 ||
			nakedCells[1].cands.Diff(pcands).Count() == 0 {
			continue
		}
		nok = true
		return
	}

	return
}

func verifyNakedType3(pivotCells, shares []*Cell, nakedHouse House) (
	nok bool, nakedCells []*Cell, nakedCands set.Set) {
	pivotCands := pivotCells[0].cands.Union(pivotCells[1].cands)
	cells := []*Cell{}
	for _, cell := range nakedHouse.cells {
		if slices.Contains(shares, cell) || cell.solved() {
			continue
		}
		cells = append(cells, cell)
	}
	for _, nakedCells = range combine(cells, 2) {
		nakedCands = nakedCells[0].cands.Union(nakedCells[1].cands)
		if nakedCands.Count() != 3 {
			continue
		}
		pcands := pivotCands.Intersect(nakedCands)
		if pcands.Count() != 3 {
			continue
		}
		nok = true
		return
	}

	return
}

func verifyHiddenType3(nakedCands set.Set, shares, pivotCells []*Cell, hiddenHouse House) (
	hok bool, hiddenCells []*Cell, hiddenCands set.Set) {
	cells := []*Cell{}
	for _, cell := range hiddenHouse.cells {
		if cell.solved() || slices.Contains(shares, cell) {
			continue
		}
		cells = append(cells, cell)
	}
	for _, hiddenCells = range combine(cells, 2) {
		otherCands := set.NewSet()
		hiddenCands = set.NewSet()
		//fmt.Println("hiddenCands", hiddenCands, "otherCands", otherCands)
		for _, cell := range hiddenHouse.cells {
			if cell.solved() || slices.Contains(pivotCells, cell) {
				continue
			}
			if slices.Contains(hiddenCells, cell) {
				hiddenCands = hiddenCands.Union(cell.cands)
			} else {
				otherCands = otherCands.Union(cell.cands)
			}
			//fmt.Println(cell, "hiddenCands", hiddenCands, "otherCands", otherCands)
		}
		//fmt.Println("hiddenCells", hiddenCells, hiddenCands, otherCands)
		hiddenCands = hiddenCands.Diff(otherCands)
		if hiddenCands != nakedCands {
			continue
		}
		hok = true
		return
	}
	return
}

func verifyNakedType6(pivotCells []*Cell, nakedHouse House) (
	nok bool, nakedCells []*Cell, nakedCands set.Set) {
	pivotCands := pivotCells[0].cands.Union(pivotCells[1].cands)
	for _, cell := range nakedHouse.cells {
		if slices.Contains(pivotCells, cell) || cell.solved() {
			continue
		}
		if cell.cands.Count() != 3 || !pivotCands.IsSuperset(cell.cands) {
			continue
		}
		nakedCands = cell.cands
		nok = true
		nakedCells = append(nakedCells, cell)
		return
	}

	return
}

func verifyHiddenType6(nakedCands set.Set, pivotCells []*Cell, hiddenHouse House) (
	hok bool, hiddenCells []*Cell, hiddenCands set.Set) {
	//fmt.Println("nakedCands", nakedCands, pivotCells)
	for _, cell := range hiddenHouse.cells {
		if slices.Contains(pivotCells, cell) || cell.solved() {
			continue
		}
		if cell.cands.Intersect(nakedCands).Count() != 0 {
			hiddenCells = append(hiddenCells, cell)
		}
	}
	if len(hiddenCells) != 1 {
		return
	}
	hok = true
	hiddenCands = nakedCands
	return
}

func (g *Grid) removeALS(shares []*Cell, nakedCells []*Cell, nakedHouse House,
	nakedCands set.Set, hiddenCells []*Cell, hiddenCands set.Set) (used bool) {
	for _, cell := range nakedHouse.cells {
		if cell.solved() || slices.Contains(shares, cell) ||
			slices.Contains(nakedCells, cell) {
			continue
		}
		if cell.removeCands(nakedCands.GetList()) {
			used = true
		}
	}

	for _, cell := range hiddenCells {
		if cell.removeCandsBut(hiddenCands.GetList()) {
			used = true
		}
	}

	return
}

func getUnsolvedCells(cells []*Cell) (unsolvedCells []*Cell) {
	for _, cell := range cells {
		if !cell.solved() {
			unsolvedCells = append(unsolvedCells, cell)
		}
	}
	return
}

func logALS(pivotCells, nakedCells []*Cell, nakedCands set.Set,
	hiddenCells []*Cell, hiddenCands set.Set, sname string) {
	var b strings.Builder

	fmt.Fprint(&b, "p")
	logCells(&b, pivotCells)

	fmt.Fprint(&b, "n")
	logCells(&b, nakedCells)
	fmt.Fprintf(&b, "%v ", nakedCands.ValueString())

	fmt.Fprint(&b, "h")
	logCells(&b, hiddenCells)

	fmt.Fprintf(&b, "%v %v", hiddenCands.ValueString(), sname)

	log.Print(b.String())
}

func logCells(b *strings.Builder, cells []*Cell) {
	for _, cell := range cells {
		fmt.Fprintf(b, "r%vc%v ", cell.rowNum, cell.colNum)
	}
}

func getAllALSHouse(house House) [][]*Cell {
	all := [][]*Cell{}
	unsolvedCells := getUnsolvedCells(house.cells[:])
	n := len(unsolvedCells)
	for i := n - 1; i >= 1; i-- {
		for _, alsa := range combine(unsolvedCells, i) {
			cands := alsa[0].cands
			for _, cell := range alsa[1:] {
				cands = cands.Union(cell.cands)
			}
			if cands.Count() != i+1 {
				continue
			}
			all = append(all, alsa)
		}
	}
	return all
}

func (g *Grid) getAllALS() [][]*Cell {
	all := [][]*Cell{}
	for _, row := range g.rows {
		all = append(all, getAllALSHouse(row)...)
	}
	for _, col := range g.cols {
		all = append(all, getAllALSHouse(col)...)
	}
	for _, box := range g.boxes {
		all = append(all, getAllALSHouse(box)...)
	}
	return all
}

func (g *Grid) AlmostLockedSetsXZ() (used bool) {
	alla := g.getAllALS()
	allb := slices.Clone(alla)
	slices.Reverse(allb)
	for _, alsa := range alla {
		for _, alsb := range allb {
			if slices.Equal(alsa, alsb) {
				continue
			}
			xzss := verifyALSXZ(alsa, alsb)
			for _, xzs := range xzss {
				x := xzs[0]
				zs := xzs[1:]
				for _, z := range zs {
					ok, removeCells := g.removeALSXZ(z, alsa, alsb)
					if ok {
						used = true
						logALSXZ(alsa, alsb, removeCells, x, z)
						return
					}
				}
			}
		}
	}
	return
}

func verifyALSXZ(alsa, alsb []*Cell) (xzss [][]int) {
	shares := shareCells(alsa, alsb)
	if len(shares) > 0 {
		return
	}

	candsa := alsa[0].cands
	for _, cell := range alsa[1:] {
		candsa = candsa.Union(cell.cands)
	}
	candsb := alsb[0].cands
	for _, cell := range alsb[1:] {
		candsb = candsb.Union(cell.cands)
	}
	xzcands := candsa.Intersect(candsb)
	if xzcands.Count() < 2 {
		return
	}

	for i, cand := range xzcands.GetList() {
		if !seenBetween(cand, alsa, alsb) {
			continue
		}
		x := cand
		zs := slices.Delete(xzcands.GetList(), i, i+1)
		xzs := append([]int{x}, zs...)
		xzss = append(xzss, xzs)
	}

	return
}

func seenBetween(cand int, alsa, alsb []*Cell) bool {
	for _, cella := range alsa {
		if !cella.cands.Has(cand) {
			continue
		}
		for _, cellb := range alsb {
			if !cellb.cands.Has(cand) {
				continue
			}
			if !sameHouse([]*Cell{cella, cellb}) {
				return false
			}
		}
	}
	return true
}

func (g *Grid) removeALSXZ(z int, alsa, alsb []*Cell) (
	used bool, removeCells []*Cell) {
	zcells := []*Cell{}
	for _, cell := range alsa {
		if cell.cands.Has(z) {
			zcells = append(zcells, cell)
		}
	}
	for _, cell := range alsb {
		if cell.cands.Has(z) {
			zcells = append(zcells, cell)
		}
	}
	shares := g.seenCells(zcells[0])
	for _, cell := range zcells[1:] {
		shares = shareCells(shares, g.seenCells(cell))
	}
	for _, cell := range shares {
		if cell.solved() {
			continue
		}
		if cell.removeCand(z) {
			removeCells = append(removeCells, cell)
			used = true
		}
	}
	return
}

func logALSXZ(alsa, alsb, removeCells []*Cell, x, z int) {
	var b strings.Builder

	logCells(&b, alsa)
	fmt.Fprint(&b, "| ")
	logCells(&b, alsb)
	fmt.Fprintf(&b, "x%v ", toValue(x))

	logCells(&b, removeCells)
	fmt.Fprintf(&b, "z%v ALS-XZ", toValue(z))
	log.Print(b.String())
}

func (g *Grid) AlmostLockedSetsXZDouble() (used bool) {
	alla := g.getAllALS()
	allb := [][]*Cell{}
	for _, alsa := range alla {
		if len(alsa) == 2 {
			allb = append(allb, alsa)
		}
	}
	for _, alsa := range alla {
		for _, alsb := range allb {
			if slices.Equal(alsa, alsb) {
				continue
			}
			xz := verifyALSXZDouble(alsa, alsb)
			if len(xz) != 2 {
				continue
			}
			removeCandCells := map[int][]*Cell{}
			for _, cand := range xz {
				ok, removeCells := g.removeALSXZ(cand, alsa, alsb)
				//fmt.Println("two", cand, removeCells)
				if ok {
					removeCandCells[cand] = append(removeCandCells[cand], removeCells...)
					used = true
				}
			}
			cands := alsb[0].cands
			for _, cell := range alsb[1:] {
				cands = cands.Union(cell.cands)
			}
			cands = cands.Diff(set.NewSet(xz))
			//fmt.Println("alsb", xz, cands.ValueString())
			for _, cand := range cands.GetList() {
				ok, removeCells := g.removeALSXZDlHouse(cand, alsb, alsa)
				if ok {
					removeCandCells[cand] = append(removeCandCells[cand], removeCells...)
					used = true
				}

			}
			cands = alsa[0].cands
			for _, cell := range alsa[1:] {
				cands = cands.Union(cell.cands)
			}
			cands = cands.Diff(set.NewSet(xz))
			//fmt.Println("alsa", xz, cands.ValueString())
			for _, cand := range cands.GetList() {
				ok, removeCells := g.removeALSXZDlHouse(cand, alsa, alsb)
				if ok {
					removeCandCells[cand] = append(removeCandCells[cand], removeCells...)
					used = true
				}

			}
			if used {
				logALSXZDouble(alsa, alsb, removeCandCells)
				return
			}
		}
	}
	return
}

func (g *Grid) removeALSXZDlHouse(cand int, alsb, alsa []*Cell) (
	used bool, removeCells []*Cell) {
	candCells := []*Cell{}
	for _, cell := range alsb {
		if cell.cands.Has(cand) {
			candCells = append(candCells, cell)
		}
	}
	houses := g.getCellsHouses(candCells)
	for _, house := range houses {
		ok, rcells := removeCandHouse(cand, alsb, alsa, house)
		if ok {
			used = true
			removeCells = append(removeCells, rcells...)
		}
	}

	return
}

func (g *Grid) getCellsHouses(cells []*Cell) (houses []House) {
	rowNum := cells[0].rowNum
	oneHouse := true
	for _, cell := range cells[1:] {
		if cell.rowNum != rowNum {
			oneHouse = false
			break
		}
	}
	if oneHouse {
		houses = append(houses, g.rows[rowNum])
	}
	colNum := cells[0].colNum
	oneHouse = true
	for _, cell := range cells[1:] {
		if cell.colNum != colNum {
			oneHouse = false
			break
		}
	}
	if oneHouse {
		houses = append(houses, g.cols[colNum])
	}
	boxNum := cells[0].boxNum
	oneHouse = true
	for _, cell := range cells[1:] {
		if cell.boxNum != boxNum {
			oneHouse = false
			break
		}
	}
	if oneHouse {
		houses = append(houses, g.boxes[boxNum])
	}
	return
}

/*
func (g *Grid) removeALSXZHouse(cand int, alsb, alsa []*Cell) (

		used bool, removeCells []*Cell) {
		//fmt.Println(alsb[0], alsb[1])
		if alsb[0].rowNum == alsb[1].rowNum {
			ok, rcells := removeCandHouse(cand, alsb, alsa, g.rows[alsb[0].rowNum])
			if ok {
				used = true
				removeCells = append(removeCells, rcells...)
			}
		}
		if alsb[0].colNum == alsb[1].colNum {
			ok, rcells := removeCandHouse(cand, alsb, alsa, g.cols[alsb[0].colNum])
			if ok {
				used = true
				removeCells = append(removeCells, rcells...)
			}
		}
		if alsb[0].boxNum == alsb[1].boxNum {
			ok, rcells := removeCandHouse(cand, alsb, alsa, g.boxes[alsb[0].boxNum])
			if ok {
				used = true
				removeCells = append(removeCells, rcells...)
			}
		}
		return
	}
*/
func removeCandHouse(cand int, alsb, alsa []*Cell, house House) (
	ok bool, removeCells []*Cell) {
	for _, cell := range house.cells {
		if cell.solved() || slices.Contains(alsb, cell) || slices.Contains(alsa, cell) {
			continue
		}
		if cell.removeCand(cand) {
			removeCells = append(removeCells, cell)
			ok = true
		}
	}
	return
}

func logALSXZDouble(alsa, alsb []*Cell, removeCandCells map[int][]*Cell) {
	var b strings.Builder

	logCells(&b, alsa)
	fmt.Fprint(&b, "| ")
	logCells(&b, alsb)
	fmt.Fprint(&b, "| ")
	for cand, cells := range removeCandCells {
		logCells(&b, cells)
		fmt.Fprintf(&b, "x%v ", toValue(cand))
	}
	fmt.Fprint(&b, "ALS-XZ Double")
	log.Print(b.String())
}

func verifyALSXZDouble(alsa, alsb []*Cell) (xz []int) {
	shares := shareCells(alsa, alsb)
	if len(shares) > 0 {
		return
	}

	candsa := alsa[0].cands
	for _, cell := range alsa[1:] {
		candsa = candsa.Union(cell.cands)
	}
	candsb := alsb[0].cands
	for _, cell := range alsb[1:] {
		candsb = candsb.Union(cell.cands)
	}
	xzcands := candsa.Intersect(candsb)

	for _, cand := range xzcands.GetList() {
		if seenBetween(cand, alsa, alsb) {
			xz = append(xz, cand)
		}
	}
	if len(xz) != 2 {
		return []int{}
	}

	return
}
